﻿#define DEBUG
//#undef DEBUG
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using System.Xml.Serialization;
using Lib;

namespace Server
{
    class Program
    {
        #region 策略请求部分-公共变量        
        static Socket sktPolicy; // 服务端监听的 socket        
        static byte[] arrPolicyClient; //收到客户端发送来的策略字符串请求 缓冲区
        static int policyReceiveByteCount; // 接收到的信息字节数(用来辅助判断字符串最否接收完整)  
        static byte[] arrPolicyServerFile;//策略文件转化成的字节数组   
        #endregion

        #region 消息监听部分-公共变量        
        static Socket sktServer;//服务端监听的 socket
        static List<ClientWrapper> lstClients = new List<ClientWrapper>(); //客户端Socket列表           
        static List<ChatUser> lstChatUsers = new List<ChatUser>();//在线用户呢称列表       
        #endregion

        static void Main(string[] args)
        {
            //Console.Write(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
            //Console.Read();

            Thread tPolicy = new Thread(StartPolicyListen);
            tPolicy.Start();

            Thread tServer = new Thread(StartServerListen);
            tServer.Start();

            Console.Read();
        }


        #region 策略请求943端口监听

        /// <summary>
        /// 启动 PolicyServer
        /// </summary>
        static void StartPolicyListen()
        {
            OutPut("(943端口)策略请求监听开始...");
            string policyFile = Path.Combine(Application.StartupPath, "Policy.xml");
            arrPolicyServerFile = File.ReadAllBytes(policyFile);

            // 初始化 socket ， 然后与端口绑定， 然后对端口进行监听
            sktPolicy = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            sktPolicy.Bind(new IPEndPoint(IPAddress.Any, 943)); // socket 请求策略文件使用 943 端口
            sktPolicy.Listen(100);

            // 开始接受客户端传入的连接
            sktPolicy.BeginAccept(new AsyncCallback(PolicyConnectComplate), null);
        }

        /// <summary>
        /// （循环调用）连接回调处理
        /// </summary>
        /// <param name="result"></param>
        static void PolicyConnectComplate(IAsyncResult result)
        {
            Socket sktClient; // 客户端发过来的 socket
            try
            {
                // 完成接受客户端传入的连接的这个异步操作，并返回客户端连入的 socket
                sktClient = sktPolicy.EndAccept(result);
            }
            catch (SocketException)
            {
                return;
            }

            arrPolicyClient = new byte[ConstParameter.POLICY_STRING.Length];//设置策略请求接收缓冲区大小

            policyReceiveByteCount = 0;

            try
            {
                // 开始接收客户端传入的数据
                sktClient.BeginReceive(arrPolicyClient, 0, ConstParameter.POLICY_STRING.Length, SocketFlags.None, new AsyncCallback(PolicyReceiveComplte), sktClient);
            }
            catch (SocketException)
            {
                // socket 出错则关闭客户端 socket
                sktClient.Close();
            }


            // 继续开始接受客户端传入的连接
            sktPolicy.BeginAccept(new AsyncCallback(PolicyConnectComplate), null);
        }


        /// <summary>
        /// 客户端策略字符串接收回调函数
        /// </summary>
        /// <param name="result"></param>
        static void PolicyReceiveComplte(IAsyncResult result)
        {
            Socket sktClient = result.AsyncState as Socket;

            try
            {
                // 完成接收数据的这个异步操作，并计算累计接收的数据的字节数
                policyReceiveByteCount += sktClient.EndReceive(result);

                if (policyReceiveByteCount < ConstParameter.POLICY_STRING.Length)
                {
                    // 没有接收到完整的数据，则继续开始接收(循环调用)
                    sktClient.BeginReceive(arrPolicyClient, policyReceiveByteCount, ConstParameter.POLICY_STRING.Length - policyReceiveByteCount, SocketFlags.None, new AsyncCallback(PolicyReceiveComplte), sktClient);
                    return;
                }

                // 把接收到的数据转换为字符串
                string _clientPolicyString = Encoding.UTF8.GetString(arrPolicyClient, 0, policyReceiveByteCount);
#if DEBUG
                OutPut("收到来自" + sktClient.RemoteEndPoint + " 的策略请求字符：" + _clientPolicyString);
#endif

                if (_clientPolicyString.ToLower().Trim() != ConstParameter.POLICY_STRING.ToLower().Trim())
                {
                    // 如果接收到的数据不是“<policy-file-request/>”，则关闭客户端 socket
                    sktClient.Close();
                    return;
                }

                // 开始向客户端发送策略文件的内容
                sktClient.BeginSend(arrPolicyServerFile, 0, arrPolicyServerFile.Length, SocketFlags.None, new AsyncCallback(PolicySendComplete), sktClient);
            }
            catch (SocketException)
            {
                // socket 出错则关闭客户端 socket
                sktClient.Close();
            }
        }

        /// <summary>
        /// 策划文件发送回调处理
        /// </summary>
        /// <param name="result"></param>
        static void PolicySendComplete(IAsyncResult result)
        {
            Socket sktClient = result.AsyncState as Socket;
            try
            {
                // 完成将信息发送到客户端的这个异步操作
                sktClient.EndSend(result);

#if DEBUG
                // 获取客户端的ip地址及端口号，并显示
                OutPut("已回发策略文件到 " + sktClient.RemoteEndPoint.ToString());
#endif
            }
            finally
            {
                // 关闭客户端 socket
                sktClient.Close();
            }
        }
        #endregion

        #region 客户端消息监听
        static void StartServerListen()
        {
            OutPut("(4518端口)socket监听服务开始...");
            // 初始化 socket ， 然后与端口绑定， 然后对端口进行监听
            sktServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            sktServer.NoDelay = true;
            sktServer.SendBufferSize = ConstParameter.BUFFER_SIZE;
            sktServer.ReceiveBufferSize = ConstParameter.BUFFER_SIZE;
            sktServer.Bind(new IPEndPoint(IPAddress.Any, 4518)); //4502-4534范围
            sktServer.Listen(100);
            sktServer.BeginAccept(new AsyncCallback(ClientConnectComplete), null);

        }

        /// <summary>
        /// 客户端连接完成的回调函数
        /// </summary>
        /// <param name="async"></param>
        static void ClientConnectComplete(IAsyncResult async)
        {
            ClientWrapper client = new ClientWrapper();

            // 完成接受客户端传入的连接的这个异步操作，并返回客户端连入的 socket
            client.Socket = sktServer.EndAccept(async);

            // 将客户端连入的 Socket 放进客户端 Socket 列表
            lstClients.Add(client);

            OutPut(client.Socket.RemoteEndPoint + " 连接成功!");

            #region 发送上线通知
            //ChatMessage msg = new ChatMessage();
            //msg.Body = UTF32Encoding.UTF8.GetBytes("欢迎 " + client.Socket.RemoteEndPoint + " 加入聊天室！");
            //msg.SenderName = "系统";
            //msg.ReceiverName = ConstParameter.BROADCAST_NAME;
            //msg.ReceiverIPAndPort = ConstParameter.BROADCAST_ENDPOINT;
            //msg.Type = ChatMessage.MessageType.txt;
            //msg.Color = "#FFFF0000";
            //MemoryStream ms = new MemoryStream();
            //XmlSerializer xml = new XmlSerializer(typeof(ChatMessage));
            //xml.Serialize(ms, msg);
            //byte[] arrMsg = ms.ToArray();
            //ms.Close();
            //List<byte> lstSend = new List<byte>();
            //lstSend.AddRange(arrMsg);
            //SendToAllClients(lstSend.ToArray(),msg);
            #endregion

            try
            {
                // 开始接收客户端传入的数据
                client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, SocketFlags.None, new AsyncCallback(ClientReceiveComplete), client);
            }
            catch (SocketException ex)
            {
                // 处理异常
                HandleException(client, ex);
            }

            sktServer.BeginAccept(new AsyncCallback(ClientConnectComplete), null);//继续接受下一个连接(关键！否则只能监听到第一个连接)
        }


        /// <summary>
        /// 客户端数据接收回调函数
        /// </summary>
        /// <param name="async"></param>
        static void ClientReceiveComplete(IAsyncResult async)
        {
            ClientWrapper client = async.AsyncState as ClientWrapper;
            int _receiveCount = 0;//用于统计本次接收到的字节数
            try
            {
                // 完成接收数据的这个异步操作，并返回接收的字节数
                if (client.Socket.Connected)
                {
                    _receiveCount = client.Socket.EndReceive(async);

                    if (_receiveCount >= 5)
                    {
                        if (client.Buffer[0] == ConstParameter.SPLIT_CHAR && client.Buffer[_receiveCount - 1] == ConstParameter.SPLIT_CHAR)//接收到的是长度
                        {
                            string _lengthStr = UTF8Encoding.UTF8.GetString(client.Buffer, 1, _receiveCount - 2);

                            if (int.TryParse(_lengthStr, out client.Length) == false)//异常情况
                            {
                                client.Length = 0;
                                client.ReceiveBytes.Clear();
                            }
                            else 
                            {
                                client.ReceiveBytes.Clear();//初始化一个干净的缓冲区，以便接收出错
                            }
                        }
                        else
                        {
                            for (int i = 0; i < _receiveCount; i++)
                            {
                                client.ReceiveBytes.Add(client.Buffer[i]);
                            }
                        }
                    }

#if DEBUG
                    OutPut("receiveCount=" + _receiveCount + "，available=" + client.Socket.Available + "，remoteEndPoint=" + client.Socket.RemoteEndPoint + "，" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:fff"));
#endif
                }
                else
                {
                    lstClients.Remove(client);
                }
            }
            catch (SocketException ex)
            {
                HandleException(client, ex);
            }
            if (sktServer.Available == 0) //全部收完了再处理
            {
                try
                {

                    if (client.ReceiveBytes.Count == client.Length && client.Length > 0)
                    {
                        #region
                        MemoryStream ms = new MemoryStream(client.ReceiveBytes.ToArray());

                        try
                        {

                            XmlSerializer xml = new XmlSerializer(typeof(ChatMessage));
                            ChatMessage msg = (ChatMessage)xml.Deserialize(ms);
                            ms.Close();

                            if (string.IsNullOrEmpty(msg.SenderIPAndPort))
                            {
                                msg.SenderIPAndPort = client.Socket.RemoteEndPoint.ToString();//得到发送者的IP和端口号
                            }




#if DEBUG
                            switch (msg.Type)
                            {
                                case ChatMessage.MessageType.txt:
                                    OutPut("收到了[" + client.Socket.RemoteEndPoint + "]于[" + DateTime.Now.ToString() + "]发来的" + msg.Body.Length + "字节的文本内容。");
                                    break;
                                case ChatMessage.MessageType.img:
                                    OutPut("收到了[" + client.Socket.RemoteEndPoint + "]于[" + DateTime.Now.ToString() + "]发来的" + msg.Body.Length + "字节的图片内容。");
                                    break;
                                case ChatMessage.MessageType.file:
                                    OutPut("收到了[" + client.Socket.RemoteEndPoint + "]于[" + DateTime.Now.ToString() + "]发来的" + msg.Body.Length + "字节的文件内容。");
                                    break;
                                case ChatMessage.MessageType.unknown:
                                default:
                                    OutPut("收到了[" + client.Socket.RemoteEndPoint + "]于[" + DateTime.Now.ToString() + "]发来的" + msg.Body.Length + "字节的UFO。");
                                    break;
                            }
#endif

                            #region //原封不动发回去
                            //SendToAllClients(client.ReceiveBytes.ToArray(),msg);
                            MemoryStream msSend = new MemoryStream();
                            xml.Serialize(msSend, msg);
                            SendToAllClients(msSend.ToArray(), msg);
                            msSend.Close();

                            Thread.Sleep(1000);//强制等本次发送全部完成，否则后面的数据会一起挤到缓冲区中，造成“粘包”
                            #endregion

                            #region 检测用户呢称是否修改
                            if (lstChatUsers.Count(c => c.EndPoint == client.Socket.RemoteEndPoint.ToString()) == 0)
                            {
                                lstChatUsers.Add(new ChatUser() { EndPoint = client.Socket.RemoteEndPoint.ToString(), NickName = msg.SenderName.Trim(), UserId = Guid.Empty });
                                SendOnListToAll();
                            }
                            else
                            {
                                ChatUser _user = lstChatUsers.FirstOrDefault(c => c.EndPoint == client.Socket.RemoteEndPoint.ToString());
                                if (_user.NickName.Trim().ToLower() != msg.SenderName.Trim().ToLower()) //改呢称了
                                {
                                    #region //发送呢称更改通知
                                    ChatMessage msgNotice = new ChatMessage();
                                    msgNotice.Body = UTF32Encoding.UTF8.GetBytes("用户[" + _user.NickName + "]将呢称改成[" + msg.SenderName.Trim() + "]了！");
                                    msgNotice.SenderName = "系统";
                                    msgNotice.ReceiverName = ConstParameter.BROADCAST_NAME;
                                    msgNotice.ReceiverIPAndPort = ConstParameter.BROADCAST_ENDPOINT;
                                    msgNotice.Color = "#FFFF0000";
                                    msgNotice.Type = ChatMessage.MessageType.txt;
                                    MemoryStream msNotice = new MemoryStream();
                                    XmlSerializer xmlNotice = new XmlSerializer(typeof(ChatMessage));
                                    xmlNotice.Serialize(msNotice, msgNotice);
                                    byte[] arrMsgNotice = msNotice.ToArray();
                                    msNotice.Close();
                                    List<byte> lstSend = new List<byte>();
                                    lstSend.AddRange(arrMsgNotice);
                                    SendToAllClients(lstSend.ToArray(),msgNotice);

                                    _user.NickName = msg.SenderName.Trim();
                                    #endregion

                                    SendOnListToAll();
                                }
                            }
                            #endregion
                        }
                        catch (Exception ex)
                        {
                            OutPut(ex.Message.ToString());
                        }
                        finally
                        {
                            client.ReceiveBytes.Clear();//清空以准备下次接收
                            client.Length = 0;
                        }
                        #endregion
                    }
                    else if (client.ReceiveBytes.Count > client.Length && client.Length > 0) 
                    {
#if DEBUG
                        OutPut("错误：client.ReceiveBytes.Count=" + client.ReceiveBytes.Count + "，client.Length=" + client.Length);
#endif
                        client.ReceiveBytes.Clear();//清空以准备下次接收
                        client.Length = 0;
                    }
                }
                catch (Exception ex)
                {
                    OutPut(ex.Message.ToString());
                }
            }

            try
            {
                //继续开始接收客户端传入的数据(关键！否则每个连接只能接受第一次发送过来的数据)
                if (client.Socket.Connected)
                {
                    client.Socket.BeginReceive(client.Buffer, 0, client.Buffer.Length, 0, new AsyncCallback(ClientReceiveComplete), client);
                }
            }
            catch (SocketException ex)
            {
                HandleException(client, ex);
            }
        }

        #region
        /// <summary>
        /// 发送数据到所有连入的客户端
        /// </summary>
        /// <param name="data">需要发送的数据</param>
        static void SendToAllClients(string data)
        {
            byte[] byteData = UTF8Encoding.UTF8.GetBytes(data);
            ChatMessage msg = new ChatMessage();
            msg.ReceiverIPAndPort = ConstParameter.BROADCAST_ENDPOINT;
            SendToAllClients(byteData,msg);
        }

        /// <summary>
        /// 发送数据到所有连入的客户端
        /// </summary>
        /// <param name="data"></param>
        static void SendToAllClients(byte[] data,ChatMessage msg)
        {
            for (int i = 0; i < lstClients.Count; i++)
            {
                ClientWrapper client = lstClients[i];
                if (client.Socket.Connected)
                {
                    if (msg.ReceiverIPAndPort == ConstParameter.BROADCAST_ENDPOINT) //群发
                    {
                        try
                        {                           

                            //先发长度
                            string _lengthData = ConstParameter.SPLIT_CHAR + data.Length.ToString() + ConstParameter.SPLIT_CHAR + (int)LengthType.ChatMessage + ConstParameter.SPLIT_CHAR;
                            byte[] _arrLength = UTF32Encoding.UTF8.GetBytes(_lengthData);
                            client.Socket.BeginSend(_arrLength, 0, _arrLength.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), client);
                            Thread.Sleep(1);
                            //再发内容
                            client.Socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), client);
                        }
                        catch (SocketException ex)
                        {
                            HandleException(client, ex);
                        }
                    }
                    else  if (client.Socket.RemoteEndPoint.ToString() == msg.ReceiverIPAndPort || client.Socket.RemoteEndPoint.ToString() == msg.SenderIPAndPort) //非群发
                    {
                        try
                        {                            
                            //先发长度
                            string _lengthData = ConstParameter.SPLIT_CHAR + data.Length.ToString() + ConstParameter.SPLIT_CHAR + (int)LengthType.ChatMessage + ConstParameter.SPLIT_CHAR;
                            byte[] _arrLength = UTF32Encoding.UTF8.GetBytes(_lengthData);
                            client.Socket.BeginSend(_arrLength, 0, _arrLength.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), client);
                            Thread.Sleep(1);
                            //再发内容
                            client.Socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendToClientComplete), client);
                        }
                        catch (SocketException ex)
                        {
                            HandleException(client, ex);
                        }
                    }
                    
                }
                else
                {
                    // 某 Socket 断开了连接的话则将其关闭，并将其清除出客户端 Socket 列表
                    // 也就是说每次向所有客户端发送消息的时候，都会从客户端 Socket 集合中清除掉已经关闭了连接的 Socket
                    client.Socket.Close();
                    ChatUser _offlineUser = lstChatUsers.FirstOrDefault(c => c.EndPoint == client.Socket.RemoteEndPoint.ToString());
                    lstChatUsers.Remove(_offlineUser);
                    lstClients.Remove(client);
                    SendOfflineMsg(_offlineUser);
                    Thread.Sleep(1);
                }
            }
        }
        #endregion

        static void SendToClientComplete(IAsyncResult async)
        {
            ClientWrapper client = async.AsyncState as ClientWrapper;
            try
            {
                // 完成将信息发送到客户端的这个异步操作
                if (client.Socket.Connected)
                {
                    client.Socket.EndSend(async);
                }
            }
            catch (SocketException ex)
            {
                HandleException(client, ex);
            }

        }

        /// <summary>
        /// 发送在线列表
        /// </summary>
        static void SendOnListToAll()
        {
            MemoryStream ms = new MemoryStream();
            XmlSerializer xml = new XmlSerializer(typeof(List<ChatUser>));
            xml.Serialize(ms, lstChatUsers);
            byte[] data = ms.ToArray();
            ms.Close();


            for (int i = 0; i < lstClients.Count; i++)
            {
                ClientWrapper client = lstClients[i];
                if (client.Socket.Connected)
                {
                    try
                    {
                        //先发长度
                        string _lengthData = ConstParameter.SPLIT_CHAR + data.Length.ToString() + ConstParameter.SPLIT_CHAR + (int)LengthType.ChatUser + ConstParameter.SPLIT_CHAR;
                        byte[] _arrLength = UTF32Encoding.UTF8.GetBytes(_lengthData);
                        client.Socket.BeginSend(_arrLength, 0, _arrLength.Length, SocketFlags.None, new AsyncCallback(SendOnListToAllComplete), client);
                        Thread.Sleep(1);
                        //再发内容
                        client.Socket.BeginSend(data, 0, data.Length, SocketFlags.None, new AsyncCallback(SendOnListToAllComplete), client);
                    }
                    catch (SocketException ex)
                    {
                        HandleException(client, ex);
                    }
                }
                else
                {
                    // 某 Socket 断开了连接的话则将其关闭，并将其清除出客户端 Socket 列表
                    // 也就是说每次向所有客户端发送消息的时候，都会从客户端 Socket 集合中清除掉已经关闭了连接的 Socket                    
                    lstChatUsers.Remove(lstChatUsers.FirstOrDefault(c => c.EndPoint == client.Socket.RemoteEndPoint.ToString()));
                    client.Socket.Close();
                    lstClients.Remove(client);
                }
            }
        }


        static void SendOnListToAllComplete(IAsyncResult async)
        {
            ClientWrapper client = async.AsyncState as ClientWrapper;
            try
            {
                // 完成将信息发送到客户端的这个异步操作
                if (client.Socket.Connected)
                {
                    client.Socket.EndSend(async);
                }
            }
            catch (SocketException ex)
            {
                HandleException(client, ex);
            }
        }

        /// <summary>
        /// 处理 SocketException 异常
        /// </summary>
        /// <param name="client">导致异常的 ClientSocketPacket</param>
        /// <param name="ex">SocketException</param>
        static void HandleException(ClientWrapper client, SocketException ex)
        {
            try
            {
                // 在服务端记录异常信息，关闭导致异常的 Socket，并将其清除出客户端 Socket 列表           
                OutPut("异常：" + client.Socket.RemoteEndPoint.ToString() + " - " + ex.Message);
                ChatUser _offlineUser = lstChatUsers.FirstOrDefault(c => c.EndPoint == client.Socket.RemoteEndPoint.ToString());
                lstChatUsers.Remove(_offlineUser);
                client.Socket.Close();
                lstClients.Remove(client);
                SendOfflineMsg(_offlineUser);
                Thread.Sleep(50);
                SendOnListToAll();
                Thread.Sleep(500);
            }
            catch { }
        }
        #endregion

        static void OutPut(object result)
        {
            // 输出客户端的ip地址及端口号
            Console.WriteLine(result.ToString());
        }


        /// <summary>
        /// 发送离线通知
        /// </summary>
        /// <param name="u"></param>
        static void SendOfflineMsg(ChatUser u) 
        {
            #region 发送下线通知
            ChatMessage msg = new ChatMessage();
            msg.Body = UTF32Encoding.UTF8.GetBytes("“" + u.NickName + "”离开了聊天室！");
            msg.SenderName = "系统";
            msg.ReceiverName = ConstParameter.BROADCAST_NAME;
            msg.ReceiverIPAndPort = ConstParameter.BROADCAST_ENDPOINT;
            msg.Color = "#FFFF0000";
            msg.Type = ChatMessage.MessageType.txt;
            MemoryStream ms = new MemoryStream();
            XmlSerializer xml = new XmlSerializer(typeof(ChatMessage));
            xml.Serialize(ms, msg);
            byte[] arrMsg = ms.ToArray();
            ms.Close();
            List<byte> lstSend = new List<byte>();
            lstSend.AddRange(arrMsg);
            SendToAllClients(lstSend.ToArray(),msg);
            #endregion
        }
    }
}
